﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LogicSystemSimulator.cs" company="pwasiewicz">
//   pwasiewicz
// </copyright>
// <summary>
//   Defines the LogicSystemSimulator type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace LogicSystem
{
    using System.Collections.Generic;
    using System.Linq;

    using LogicSystem.Exceptions;
    using LogicSystem.Interfaces;
    using LogicSystem.LogicGates;
    using LogicSystem.Wires;

    /// <summary>The logic system simulator.</summary>
    public class LogicSystemSimulator
    {
        /// <summary>Initializes a new instance of the <see cref="LogicSystemSimulator"/> class.</summary>
        public LogicSystemSimulator()
        {
            this.InputWires = Enumerable.Empty<Wire>().ToList();
        }

        /// <summary>
        /// Gets or sets the output wire.
        /// </summary>
        public Wire OutputWire { get; set; }

        /// <summary>
        /// Gets or sets simulating environment.
        /// </summary>
        internal ISimulatingEnvironment<int, LogicGate> SimulatingEnvironment { get; set; }

        /// <summary>
        /// Gets or sets input wires.
        /// </summary>
        protected List<Wire> InputWires { get; set; }

        /// <summary>
        /// Adds input wires.
        /// </summary>
        /// <param name="wires">Wires array.</param>
        public void AddInputWires(params Wire[] wires)
        {
            if (wires.Any(wire => !ReferenceEquals(
                                    wire.SimulatingEnvironment,
                                    this.SimulatingEnvironment)))
            {
                throw new LogicElementsFactoryDismatchException();
            }

            this.InputWires.AddRange(wires);
        }

        /// <summary>
        /// Adds input wires.
        /// </summary>
        /// <param name="wires">Collection of wires</param>
        public void AddInputWires(IEnumerable<Wire> wires)
        {
            this.AddInputWires(wires.ToArray());
        }

        /// <summary>Simulates the logic system workflow.
        /// Output wire's state is set the the logical
        /// system value.</summary>
        /// <param name="inputStates">The input States.</param>
        /// <returns>Time needed to compute whole system.</returns>
        public int Simulate(params bool[] inputStates)
        {
            if (inputStates.Count() != this.InputWires.Count())
            {
                throw new ArgumentsQuantityDismatchException();
            }

            this.CheckOriginFactory(this.InputWires);

            this.SimulatingEnvironment.Reset();

            for (var i = 0; i < this.InputWires.Count; i++)
            {
                this.InputWires[i].State = inputStates[i];
            }

            while (!this.SimulatingEnvironment.IsEmpty())
            {
                var gates = this.GetLowestPriority();
                var logicGates = gates as IList<LogicGate> ?? gates.ToList();

                this.CheckOriginFactory(logicGates);

                this.SimulatingEnvironment.TotalTime += logicGates.ElementAt(0).Latency;

                foreach (var gate in logicGates)
                {
                    gate.Refresh();
                }
            }

            return this.SimulatingEnvironment.TotalTime;
        }

        /// <summary>Simulates the logic system workflow.
        /// Output wire's state is set the the logical
        /// system value.</summary>
        /// <param name="inputStates">The input States.</param>
        /// <returns>Time needed to compute whole system.</returns>
        public int Simulate(IEnumerable<bool> inputStates)
        {
            return this.Simulate(inputStates.ToArray());
        }

        /// <summary>
        /// Gets collection of gates with the lowest priority.
        /// </summary>
        /// <returns>LogicGate instances collection.</returns>
        private IEnumerable<LogicGate> GetLowestPriority()
        {
            var topPair = this.SimulatingEnvironment.PopFromHeap();

            var output = new List<LogicGate> { topPair.Value };

            while (!this.SimulatingEnvironment.IsEmpty() &&
                        this.SimulatingEnvironment.Top().Key == topPair.Key)
            {
                output.Add(this.SimulatingEnvironment.PopFromHeap().Value);
            }

            return output;
        }


        /// <summary>
        /// Checks the wires origin factory.
        /// </summary>
        /// <param name="wires">The wires.</param>
        /// <exception cref="LogicElementsFactoryDismatchException">Thrown, when factory doesn't match.</exception>
        private void CheckOriginFactory(IEnumerable<Wire> wires)
        {
            if (wires.Any(wire => !ReferenceEquals(wire.SimulatingEnvironment, this.SimulatingEnvironment)))
            {
                throw new LogicElementsFactoryDismatchException();
            }
        }

        /// <summary>
        /// Checks the origin factory.
        /// </summary>
        /// <param name="gates">The gates.</param>
        /// <exception cref="LogicElementsFactoryDismatchException">Thrown, when factory doesn't match.</exception>
        private void CheckOriginFactory(IEnumerable<LogicGate> gates)
        {
            var logicGates = gates as IList<LogicGate> ?? gates.ToList();
            if (logicGates.Any(gate => !ReferenceEquals(gate.SimulatingEnvironment, this.SimulatingEnvironment)))
            {
                throw new LogicElementsFactoryDismatchException();
            }

            foreach (var logicGate in logicGates)
            {
                this.CheckOriginFactory(logicGate.InputWires);
            }
        }

    }
}
